package com.qzuser.sendmessagebasic.server.dao;

import com.qzuser.sendmessagebasic.common.exception.MessageFileException;
import com.qzuser.sendmessagebasic.common.exception.ToolException;
import com.qzuser.sendmessagebasic.common.tool.BinaryTool;
import com.qzuser.sendmessagebasic.server.message.Message;
import com.qzuser.sendmessagebasic.server.queue.Queue;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

public class MessageFile {
    /**
     * 内部类定义统计文件中总的消息以及有效消息
     * validCount / total < 0.5 ? gc : no
     */
    public static class Stat {
        public int totalCount;
        public int validCount;
    }

    // todo: 留待后续扩展
    public void init() {
    }

    private String getDir(String queueName) {
        return "./data/" + queueName;
    }

    private String getDataPath(String queueName) {
        return getDir(queueName) + "/queue_data.txt";
    }

    private String getStatPath(String queueName) {
        return getDir(queueName) + "/queue_stat.txt";
    }

    public void createFiles(String queueName) throws MessageFileException {
        File baseDir = new File(getDir(queueName));
        if (!baseDir.exists()) {
            boolean ok = baseDir.mkdirs();
            if (!ok) {
                throw new MessageFileException("[MessageFile] 文件目录创建失败：" + baseDir.getAbsolutePath());
            }
        }
        File dataFile = new File(getDataPath(queueName));
        if (!dataFile.exists()) {
            try {
                boolean ok = dataFile.createNewFile();
            } catch (IOException e) {
                throw new MessageFileException("[MessageFile] 统计文件创建失败：" + dataFile.getAbsolutePath());
            }
        }
        File statFile = new File(getStatPath(queueName));
        if (!statFile.exists()) {
            boolean ok = false;
            try {
                ok = statFile.createNewFile();
            } catch (IOException e) {
                throw new MessageFileException("[MessageFile] 统计文件创建失败：" + statFile.getAbsolutePath());
            }
        }
        Stat stat = new Stat();
        stat.totalCount = 0;
        stat.validCount = 0;
        writeStat(stat, queueName);
    }

    public void writeStat(Stat stat, String queueName) throws MessageFileException {
        try (OutputStream OutputStream = new FileOutputStream(getStatPath(queueName))) {
            PrintWriter writer = new PrintWriter(OutputStream);
            writer.write(stat.totalCount + "\t" + stat.validCount);
            writer.flush();
        } catch (IOException e) {
            throw new MessageFileException("[MessageFile] 统计文件写入失败：" + getStatPath(queueName));
        }
    }

    public Stat readStat(String queueName) throws MessageFileException {
        Stat stat = new Stat();
        try(InputStream inputStream = new FileInputStream(getStatPath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            throw new MessageFileException("[MessageFile] 统计文件读取失败：" + getStatPath(queueName));
        }
    }

    public void deleteFiles(String queueName) throws MessageFileException {
        File dataFile = new File(getDataPath(queueName));
        File statFile = new File(getStatPath(queueName));
        boolean ok1 = dataFile.delete();
        boolean ok2 = statFile.delete();
        File dir = new File(getDir(queueName));
        boolean ok3 = dir.delete();
        if (!ok1 || !ok2 || !ok3) {
            throw new MessageFileException("[MessageFile] 文件删除失败：" + getDir(queueName));
        }
    }

    public void sendMessage(Queue queue, Message message) throws MessageFileException, ToolException {
       if (!checkFileExists(queue.getName())) {
           throw new MessageFileException("[MessageFile] 当前队列文件不存在：" + getDataPath(queue.getName()));
       }
       byte[] msgByteArray = BinaryTool.toByteArray(message);
       synchronized (queue) {
           File dataFile = new File(getDataPath(queue.getName()));
           message.setOffsetBeg(dataFile.length() + 4);
           message.setOffsetEnd(dataFile.length() + 4 + msgByteArray.length);
           try (FileOutputStream fileOutputStream = new FileOutputStream(getDataPath(queue.getName()), true)) {
               try (DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) {
                   dataOutputStream.writeInt(msgByteArray.length);
                   dataOutputStream.write(msgByteArray);
               }
           } catch (Exception e) {
               throw new MessageFileException("[MessageFile] 数据文件写入失败：" + getDataPath(queue.getName()));
           }
           Stat stat = readStat(queue.getName());
           if (stat != null) {
               stat.totalCount++;
               stat.validCount++;
               writeStat(stat, queue.getName());
           }
       }
    }

    private boolean checkFileExists(String name) {
        File dataFile = new File(getDataPath(name));
        File statFile = new File(getStatPath(name));
        return dataFile.exists() && statFile.exists();
    }

    public void deleteMessage(Queue queue, Message message) throws MessageFileException {
        synchronized (queue) {
            try (RandomAccessFile readFile = new RandomAccessFile(getDataPath(queue.getName()), "rw")) {
                byte[] msgByteArray = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];
                readFile.seek(message.getOffsetBeg());
                readFile.read(msgByteArray);
                Message msg = (Message) BinaryTool.fromByteArray(msgByteArray);
                msg.setIsValid((byte) 0x0);
                byte[] byteArray = BinaryTool.toByteArray(msg);
                readFile.seek(message.getOffsetBeg());
                readFile.write(byteArray);
                Stat stat = readStat(queue.getName());
                if (stat != null && stat.validCount > 0) {
                    stat.validCount--;
                    writeStat(stat, queue.getName());
                }
            } catch (Exception e) {
                throw new MessageFileException("[MessageFile] 数据文件消息删除失败" + getDataPath(queue.getName()));
            }
        }
    }

    public void gc(Queue queue, MemoryCenter memoryCenter) throws MessageFileException {
        synchronized (queue) {
            long gcStartTime = System.currentTimeMillis();
            File file = new File(getNewDataPath(queue.getName()));
            if (file.exists()) {
                throw new MessageFileException("[MessageFile] 临时数据文件存在，gc异常：" + getNewDataPath(queue.getName()));
            }
            try {
                boolean ok = file.createNewFile();
            } catch (IOException ignored) {
                throw new MessageFileException("[MessageFile] 临时数据文件创建失败：" + getNewDataPath(queue.getName()));
            }
            LinkedList<Message> msgList = loadOldFile(queue.getName());
            try (DataOutputStream dis = new DataOutputStream(new FileOutputStream(getNewDataPath(queue.getName()), true))) {
                for (Message msg : msgList) {
                    byte[] msgByteArray = BinaryTool.toByteArray(msg);
                    long beg = file.length() + 4;
                    long end = file.length() + 4 + msgByteArray.length;
                    dis.writeInt(msgByteArray.length);
                    dis.write(msgByteArray);
                    LinkedList<Message> messages = memoryCenter.getMessages(queue.getName());
                    if (messages != null) {
                        for (Message message : messages) {
                            if (message.getMessageId().equals(msg.getMessageId())) {
                                message.setOffsetBeg(beg);
                                message.setOffsetEnd(end);
                            }
                        }
                    }
                    Message msgMap = memoryCenter.getMessage(msg.getMessageId());
                    if (msgMap != null) {
                        msgMap.setOffsetBeg(beg);
                        msgMap.setOffsetEnd(end);
                    }
                    Message waitAckMsg = memoryCenter.getWaitAckMsg(queue.getName(), msg.getMessageId());
                    if (waitAckMsg != null) {
                        waitAckMsg.setOffsetBeg(beg);
                        waitAckMsg.setOffsetEnd(end);
                    }
                }
            } catch (Exception e) {
                throw new MessageFileException("[MessageFile] 临时数据文件写入数据失败：" + getNewDataPath(queue.getName()));
            }

            File oldFile = new File(getDataPath(queue.getName()));
            boolean ok = oldFile.delete();
            boolean ok2 = file.renameTo(oldFile);
            if (!ok || !ok2) {
                throw new MessageFileException("[MessageFile] 数据文件迁移失败：" + getDataPath(queue.getName()));
            }
            Stat stat = readStat(queue.getName());
            stat.validCount = msgList.size();
            stat.totalCount = msgList.size();
            writeStat(stat, queue.getName());
            long gcEndTime = System.currentTimeMillis();
            System.out.println("[MessageFile] " + getDataPath(queue.getName()) + "，gc执行完毕，耗时："  + (gcEndTime - gcStartTime) + "ms");
        }
    }

    public LinkedList<Message> loadOldFile(String queueName) throws MessageFileException {
        LinkedList<Message> msgList = new LinkedList<>();
        try (DataInputStream dis = new DataInputStream(new FileInputStream(getDataPath(queueName)))) {
            long curOffset = 0L;
            while (dis.available() > 0) {
                if (dis.available() < 4) {
                    break;
                }
                int len = dis.readInt();
                byte[] msgByteArray = new byte[len];
                dis.readFully(msgByteArray);
                Message msg = (Message) BinaryTool.fromByteArray(msgByteArray);
                if (msg.getIsValid() != (byte) 0x1) {
                    curOffset += (4 + len);
                    continue;
                }
                msg.setOffsetBeg(curOffset + 4);
                msg.setOffsetEnd(curOffset + 4 + len);
                msgList.add(msg);
                curOffset += (len + 4);
            }
        } catch (Exception e) {
            throw new MessageFileException("[MessageFile] 读取有效消息失败：" + getDataPath(queueName));
        }
        return msgList;
    }

    private String getNewDataPath(String queueName) {
        return getDir(queueName) + "/queue_data_new.txt";
    }

    public boolean checkGc(String queueName) throws MessageFileException {
        Stat stat = readStat(queueName);
        return stat.totalCount > 2000 && (double) (stat.validCount / stat.totalCount) < 0.5;
    }
}
